<!DOCTYPE html>
<html>
  <head>
    <meta content="text/html; charset=UTF-8" http-equiv="content-type">
    <title>ch03-02</title>
    <link href="css/style.css" rel="stylesheet" type="text/css">
    <link rel="stylesheet" href="thumbnailviewer.css" type="text/css">
    <script src="thumbnailviewer.js" type="text/javascript">

/***********************************************
* Image Thumbnail Viewer Script- © Dynamic Drive (www.dynamicdrive.com)
* This notice must stay intact for legal use.
* Visit http://www.dynamicdrive.com/ for full source code
***********************************************/

</script> </head>
  <body>
    <div class="os1">3.2 QAudioOutput音频播放 </div>
    <br>
    本节介绍利用 QAudioOutput 播放原始 PCM 数据流文件，音频输出 QAudioOutput 与 音频输入 QAudioInput
    的大部分函数都很相似，区别仅仅是一个输出、另一个输入。 QAudioOutput 输出时也需要指定音频输出设备 QAudioDeviceInfo
    和音频格式 QAudioFormat，音频设备和音频格式的内容参考前面一节。 <br>
    <br>
    本节内容安排：3.2.1 介绍 QAudioOutput 音频输出类的内容。 3.2.2 节学习一个播放声音文件示例，展示如何利用
    QAudioOutput 播放一个本地的原始 PCM 文件。 3.2.3 节学习一个播放网络数据源的示例，从 UDP 端口接收原始 PCM
    数据进行播放，并进行简单录音。 <br>
    <br>
    <div class="os2">3.2.1 QAudioOutput 功能 </div>
    <br>
    音频播放时也需要选择输出设备，获取音频输出设备的静态函数如下：<br>
    <div class="code">QList&lt;QAudioDeviceInfo&gt;
      QAudioDeviceInfo::​availableDevices(QAudio::Mode mode)</div>
    将参数 mode 设置为 QAudio::AudioOutput 就是获取输出设备信息列表。<br>
    获取系统默认的音频输出设备，可以用下面静态函数：<br>
    <div class="code">QAudioDeviceInfo QAudioDeviceInfo::​defaultOutputDevice()</div>
    播放音频时使用的音频格式与采样时的音频格式 QAudioFormat 一样。<br>
    <br>
    下面我们对 QAudioOutput 功能函数分类介绍：<br>
    （1）构造函数和析构函数<br>
    <div class="code">QAudioOutput(const QAudioFormat &amp; format =
      QAudioFormat(), QObject * parent = 0)<br>
      QAudioOutput(const QAudioDeviceInfo &amp; audioDevice, const QAudioFormat
      &amp; format = QAudioFormat(), QObject * parent = 0)<br>
      ~QAudioOutput() //析构函数 </div>
    第一个构造函数，参数只有音频格式和父对象指针，采用系统默认的音频输出设备播放音频数据，音频格式用参数 format 指定；<br>
    第二个构造函数同时指定了输出设备 audioDevice 和音频格式 format，使用指定设备播放音频数据。<br>
    第三个是析构函数，释放音频输出占用的资源。<br>
    <br>
    （2）预先设置函数<br>
    下面这些预先设置函数需要在播放开始之前就指定好，播放一旦开始就不能修改。<br>
    设置音频输出的缓冲区大小，使用下面函数：<br>
    <div class="code">void setBufferSize(int value)&nbsp; //设置输出缓冲区大小<br>
      int bufferSize() const //获取输出缓冲区大小</div>
    注意不能假定设置的缓冲区大小就是播放时真实缓冲区大小，因为声音播放与操作系统和声卡硬件有关，指定的缓冲区大小数值不一定有用，缓冲区大小可能会被系统修改为
    其他数值。在播放开始之后可以用 bufferSize() 查看播放实际用的缓冲区大小。<br>
    <br>
    音频输出通常将缓冲区分为几片小缓冲区，轮流输出到声卡设备，每个小片缓冲区的大小是建议程序分片输出的音频数据大小，用如下函数获取小片缓冲区的字节数：<br>
    <div class="code">int periodSize() const</div>
    periodSize() 由Qt库自己控制，没有函数可以直接修改 periodSize() 数值。<br>
    <br>
    设置某些平台的声效分类，使用下面函数：<br>
    <div class="code">void setCategory(const QString &amp; category) //设置声效分类<br>
      QString category() const&nbsp; //获取声效分类</div>
    某些系统平台对各个音频输出流可以进行分类定制，为不同声效类别的输出流设置整体的音量、混音器等等。声效分类函数一般用不到，如果系统不支持声效分类，设置声效
    分类的函数调用就会被忽略。<br>
    <br>
    音频输出也可以设置定时通知信号，用于检查工作状态或者定时写入数据到声卡：<br>
    <div class="code">void setNotifyInterval(int ms)&nbsp; //设置定时通知的时间间隔，毫秒时间<br>
      int notifyInterval() const&nbsp; //查询定时通知的时间间隔</div>
    设置定时通知之后，每隔指定毫秒时间就会触发 notify() 信号，将槽函数关联到该信号就会定时调用该槽函数。<br>
    <br>
    设置播放音量，使用下面函数：<br>
    <div class="code">void setVolume(qreal volume) //设置播放音量，0.0 ~ 1.0<br>
      qreal volume() const&nbsp;&nbsp; //获取播放音量</div>
    音量设置范围是 0.0 到 1.0 ，这个音量设置仅修改当前音频输出流的音量，不会修改系统全局的音量。<br>
    <br>
    （3）开始、暂停、恢复、结束播放的函数<br>
    音频输出对象的开始播放函数也有两个：<br>
    <div class="code">void start(QIODevice * device) <br>
      QIODevice *&nbsp; start()</div>
    第一个开始函数以参数里的设备作为数据源，参数里可以是文件或者网络套接字，音频输出对象自动从文件或套接字获取数据并输出到声卡，进行音频播放。第一个函数涉及
    的原始 PCM 数据分片完全不经过程序员处理，都由参数里的设备和音频输出对象全自动处理。<br>
    第二个函数没有设备参数，而是将内存缓冲设备作为返回值，程序员周期性地向内存缓冲区设备写 PCM
    数据分片，实现声音数据播放。内存缓冲设备由音频输出对象管理，不要手动删除内存缓冲设备对象指针。<br>
    <br>
    音频输出开始之后，可以进行播放暂停或继续：<br>
    <div class="code">void suspend() //暂停<br>
      void resume()&nbsp; //继续</div>
    如果需要清空输出缓冲区，重置音频输出对象，使用如下函数：<br>
    <div class="code">void reset()</div>
    结束音频播放使用下面函数：<br>
    <div class="code">void&nbsp; stop()</div>
    <br>
    （4）状态和信息查询函数<br>
    进行音频播放时，可以实时查询音频输出对象的当前工作状态：<br>
    <div class="code">QAudio::State state() const</div>
    音频输出对象的工作状态划分与音频输入对象是一样的：<br>
    <br>
    <table class="tabel">
      <tbody>
        <tr class="d1">
          <td style="width: 220px;" align="center"><b>QAudio::​State 枚举常量</b></td>
          <td style="width: 80px;" align="center"><b>数值</b></td>
          <td align="center"><b> 描述</b></td>
        </tr>
        <tr>
          <td>QAudio::ActiveState</td>
          <td> 0 </td>
          <td> 活跃状态，设备正在进行录音或播放。 </td>
        </tr>
        <tr class="d1">
          <td>QAudio::SuspendedState</td>
          <td> 1</td>
          <td> 暂停状态，只有suspend()函数才会触发暂停状态 。 </td>
        </tr>
        <tr>
          <td>QAudio::StoppedState</td>
          <td> 2</td>
          <td> 停止状态，设备还没开始工作或者已经停止工作。 </td>
        </tr>
        <tr class="d1">
          <td>QAudio::IdleState</td>
          <td> 3</td>
          <td> 空闲状态，设备正在工作，但缓冲区里没有数据需要处理 。 </td>
        </tr>
      </tbody>
    </table>
    <br>
    当音频输出对象的工作状态变化时，会触发 stateChanged(QAudio::State state) 信号。<br>
    如果音频输出对象出现错误，可以查询错误代号：<br>
    <div class="code">QAudio::Error error() const</div>
    音频输出对象的错误代号与音频输入对象也是一样的：<br>
    <br>
    <table class="tabel">
      <tbody>
        <tr class="d1">
          <td style="width: 220px;" align="center"><b>QAudio::​Error 枚举常量</b></td>
          <td style="width: 80px;" align="center"><b>数值</b></td>
          <td align="center"><b> 描述</b></td>
        </tr>
        <tr>
          <td>QAudio::NoError</td>
          <td> 0 </td>
          <td> 没有出错。 </td>
        </tr>
        <tr class="d1">
          <td>QAudio::OpenError</td>
          <td> 1</td>
          <td> 打开音频设备出错。 </td>
        </tr>
        <tr>
          <td>QAudio::IOError</td>
          <td> 2</td>
          <td> 读取或写入音频设备时出错。 </td>
        </tr>
        <tr class="d1">
          <td>QAudio::UnderrunError</td>
          <td> 3</td>
          <td> 数据不足，数据供给速率偏低，没有及时并且足量地提供音频数据给音频设备 。 </td>
        </tr>
        <tr>
          <td>QAudio::FatalError</td>
          <td> 4</td>
          <td> 致命错误，发生了无法修复的错误，音频设备目前无法使用。 </td>
        </tr>
      </tbody>
    </table>
    <br>
    除了 NoError 和 UnderrunError，其他三个错误都会导致音频设备无法正常工作。<br>
    <br>
    当音频输出对象开始工作之后，可以查询已经过去的时间：<br>
    <div class="code">qint64 elapsedUSecs() const&nbsp; //从开始到现在的总时间，单位微秒</div>
    elapsedUSecs() 统计从开始到现在消逝的总时间，包含空闲和暂停时间。<br>
    如果查询不包括空闲和暂时状态的实际处理数据时间，使用下面函数：<br>
    <div class="code">qint64&nbsp; processedUSecs() const&nbsp; //实际处理数据的时间，不计空闲
      和暂停状态时间，单位微秒</div>
    <br>
    获取当前音频格式的函数为：<br>
    <div class="code">QAudioFormat format() const</div>
    音频格式在构造函数里指定，之后不能修改。<br>
    <br>
    在进行音频输出时，如果程序员手动控制输出的时间片和数据片段，那么要注意数据供给速率不能太快，如果数据供给太快，那么导致内存缓冲设备的数据溢出，多出的数据
    就会被丢弃，导致播放数据不完整。查询内存缓冲设备可用的剩余空间，使用下面函数：<br>
    <div class="code">int bytesFree() const</div>
    向内存缓冲设备写数据时，数据片段的长度不要超过 bytesFree() 的数值。注意只有在音频输出对象工作在 QAudio::ActiveState
    或者 QAudio::IdleState 状态时， bytesFree() 数值才有效，其他状态的bytesFree()数值都是
    0，要注意判断当前工作状态和 bytesFree() 返回值。 <br>
    <br>
    （5）信号通知函数<br>
    音频输出对象设置定时通知的时间间隔后，就会定时触发下面信号：<br>
    <div class="code">void&nbsp; notify()</div>
    将槽函数关联到该信号，可以定时查询工作状态、文件播放进度或者定时输出数据片段到音频输出缓冲区。<br>
    当前音频输出对象的工作状态变化时，会触发下面信号<br>
    <div class="code">void stateChanged(QAudio::State state)</div>
    将槽函数关联到该信号，可以实时监控工作状态，检查是否有错误发生等等。<br>
    QAudioOutput 的功能函数介绍到这，下面两个小节我们通过示例学习如何播放本地文件和网络数据。<br>
    <br>
    <div class="os2">3.2.2 播放本地 PCM 文件示例 </div>
    <br>
    <br>
    <br>
    <div class="os2">3.2.3 播放网络 PCM 数据示例 </div>
    <br>
    <br>
    <br>
    <br>
    <table style="text-align: left; width: 100%;" border="0" cellpadding="2" cellspacing="2">
      <tbody>
        <tr>
          <td style="width: 40%;">
            <div style="text-align: center;"><a href="ch03-01.htm"><img class="pic"
                  style="width: 32px; height: 32px;" alt="prev" src="images/pics/prev.png"></a></div>
          </td>
          <td style="width: 20%;">
            <div style="text-align: center;"><a href="contents.htm"><img class="pic"
                  style="width: 32px; height: 32px;" alt="contents" src="images/pics/contents.png"></a></div>
          </td>
          <td style="width: 40%;">
            <div style="text-align: center;"><a href="ch03-03.htm"><img class="pic"
                  style="width: 32px; height: 32px;" alt="next" src="images/pics/next.png"></a></div>
          </td>
        </tr>
      </tbody>
    </table>
  </body>
</html>
